package cn.elead.chaos.shiro.configuration;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.authc.Authenticator;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.mgt.SessionStorageEvaluator;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSessionStorageEvaluator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import cn.elead.chaos.core.util.StringUtils;
import cn.elead.chaos.framework.properties.ChaosProperties;
import cn.elead.chaos.framework.properties.PathDefinition;
import cn.elead.chaos.shiro.filter.JwtAuthFilter;
import cn.elead.chaos.shiro.realm.JWTShiroRealm;
import cn.elead.chaos.shiro.service.IShiroRealmService;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;

/**
 * 权限配置加载
 * <p>
 * https://www.jianshu.com/p/0b1131be7ace<br>
 * https://github.com/chilexun/springboot-demo
 * 
 * @author luopeng
 */
@Configuration
public class ShiroAuoConfiguration implements WebMvcConfigurer {

	@Autowired
	private ChaosProperties properties;
	
	@Autowired
	private HttpServletRequest request;

	/**
	 * 注册shiro的Filter，拦截请求
	 */
	@Bean
	public FilterRegistrationBean<Filter> filterRegistrationBean(SecurityManager securityManager) throws Exception {
		FilterRegistrationBean<Filter> filterRegistration = new FilterRegistrationBean<Filter>();
		filterRegistration.setFilter((Filter) shiroFilter(securityManager).getObject());
		filterRegistration.addInitParameter("targetFilterLifecycle", "true");
		filterRegistration.setAsyncSupported(true);
		filterRegistration.setEnabled(true);
		filterRegistration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);

		return filterRegistration;
	}

	/**
	 * 初始化Authenticator
	 */
	public Authenticator authenticator(IShiroRealmService shiroRealmService) {
		ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
		// 设置两个Realm，一个用于用户登录验证和访问权限获取；一个用于jwt token的认证
		// authenticator.setRealms(Arrays.asList(jwtShiroRealm(userService), dbShiroRealm(userService)));
		authenticator.setRealms(Arrays.asList(jwtShiroRealm(shiroRealmService)));
		authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
		return authenticator;
	}

	/**
	 * 禁用session, 不保存用户登录状态。保证每次请求都重新认证。 需要注意的是，如果用户代码里调用Subject.getSession()还是可以用session，<br>
	 * 如果要完全禁用，要配合下面的noSessionCreation的Filter来实现
	 */
	@Bean
	protected SessionStorageEvaluator sessionStorageEvaluator() {
		DefaultWebSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();
		sessionStorageEvaluator.setSessionStorageEnabled(false);
		return sessionStorageEvaluator;
	}

	/**
	 * 用于JWT token认证的realm
	 */
	@Bean("jwtRealm")
	public Realm jwtShiroRealm(IShiroRealmService shiroRealmService) {
		JWTShiroRealm myShiroRealm = new JWTShiroRealm(shiroRealmService, request);
		return myShiroRealm;
	}

	/**
	 * 设置过滤器，将自定义的Filter加入
	 */
	@Bean("shiroFilter")
	public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
		ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
		factoryBean.setSecurityManager(securityManager);
		// 401需要登录的接口,如果访问某个接口,需要登录却没有登录,则调用此接口(如果不是前后端分离,则跳转页面)
		// factoryBean.setLoginUrl("/shiro/need_login");
		// 登录成功,跳转URL如果前后端分离,则没有这个调用
		// factoryBean.setSuccessUrl("/");
		// 403没有权限,未授权就会调用此接口,先验证登录->再验证是否有权限
		// factoryBean.setUnauthorizedUrl("/shiro/not_permit");

		Map<String, Filter> filterMap = factoryBean.getFilters();
		filterMap.put("authcToken", createAuthFilter());
		factoryBean.setFilters(filterMap);
		factoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition().getFilterChainMap());

		return factoryBean;
	}

	/**
	 * authc | 需要认证登录才能访问 user | 用户拦截器,表示必须存在用户 anon | 匿名拦截器,不需要登录即可访问的资源,匿名用户或是游客,一般用于过滤静态资源 roles | 角色授权拦截器,验证用户是否拥有角色,参数可写多个,多个参数是写roles["admin,user"],当有多个参数时必须每个参数都通过才算通过 perms |
	 * 权限授权拦截器,验证用户是否拥有权限,参数可写多个,多个参数是写perms["admin,user"],当有多个参数时必须每个参数都通过才算通过 authcBasic | httpBasic身份验证拦截器 logout | 退出拦截器,执行后跳转到shiroFilterFactoryBean.setLoginUrl();设置的URL port | 端口拦截器,可通过的端口 ssl |
	 * ssl拦截器,只有请求协议是https才能通过 noSessionCreation | Session拦截器,禁止创建会话 自定义 | 自定义权限授权拦截器 https://segmentfault.com/a/1190000014479154?utm_source=tag-newest https://www.jianshu.com/p/0b1131be7ace
	 */
	@Bean
	protected ShiroFilterChainDefinition shiroFilterChainDefinition() {
		DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
		// 对静态资源设置匿名访问
		chainDefinition.addPathDefinition("/favicon.ico**", "noSessionCreation, anon");
		chainDefinition.addPathDefinition("/css/**", "noSessionCreation, anon");
		chainDefinition.addPathDefinition("/docs/**", "noSessionCreation, anon");
		chainDefinition.addPathDefinition("/fonts/**", "noSessionCreation, anon");
		chainDefinition.addPathDefinition("/img/**", "noSessionCreation, anon");
		chainDefinition.addPathDefinition("/ajax/**", "noSessionCreation, anon");
		chainDefinition.addPathDefinition("/js/**", "noSessionCreation, anon");
		chainDefinition.addPathDefinition("/web/**", "noSessionCreation, anon");
		// swagger接口权限 开放
		chainDefinition.addPathDefinition("/doc.html", "noSessionCreation, anon");
		chainDefinition.addPathDefinition("/swagger-ui.html", "noSessionCreation, anon");
		chainDefinition.addPathDefinition("/webjars/**", "noSessionCreation, anon");
		chainDefinition.addPathDefinition("/v2/api-docs", "noSessionCreation, anon");
		chainDefinition.addPathDefinition("/swagger-resources/**", "noSessionCreation, anon");
		chainDefinition.addPathDefinition("/druid/**", "noSessionCreation, anon");

		chainDefinition.addPathDefinition("/profile/**", "noSessionCreation, anon");
		chainDefinition.addPathDefinition("/common/download**", "noSessionCreation, anon");

		// 扩展匿名访问
		if (ObjectUtil.isNotEmpty(properties) && ObjectUtil.isNotEmpty(properties.getSecurity())) {
			List<PathDefinition> antPaths = properties.getSecurity().getAnonPaths();
			if (CollectionUtil.isNotEmpty(antPaths)) {
				for (PathDefinition antPath : antPaths) {
					String path = antPath.getAntPath();
					String definition = StringUtils.defaultValue(antPath.getDefinition(), "noSessionCreation, anon");
					if (StrUtil.isNotBlank(path)) {
						chainDefinition.addPathDefinition(path, definition);
					}
				}
			}
		}

		// 退出 logout地址，shiro去清除session
		// chainDefinition.addPathDefinition("/logout", "logout");
		// 不需要拦截的访问
		// chainDefinition.addPathDefinition("/login", "noSessionCreation, anon");
		// 验证码
		// chainDefinition.addPathDefinition("/captcha", "noSessionCreation, anon");

		// 所有请求需要认证
		if (ObjectUtil.isNotEmpty(properties) && ObjectUtil.isNotEmpty(properties.getSecurity())) {
			List<PathDefinition> authPaths = properties.getSecurity().getAuthPaths();
			if (CollectionUtil.isNotEmpty(authPaths)) {
				for (PathDefinition authPath : authPaths) {
					String path = authPath.getAntPath();
					String definition = StringUtils.defaultValue(authPath.getDefinition(), "noSessionCreation, authcToken");
					chainDefinition.addPathDefinition(path, definition);
				}
			}
		} else {
			chainDefinition.addPathDefinition("/**", "noSessionCreation, authcToken");
		}
		return chainDefinition;
	}

	// 注意不要加@Bean注解，不然spring会自动注册成filter
	protected JwtAuthFilter createAuthFilter() {
		return new JwtAuthFilter(properties.getSecurity());
	}

}
